#include "stdafx.h"
#include "textPrinter.h"
#include "layoutDesc.h"
#include "layoutParser.h"
#include "layoutDependencyAnalyzer.h"
#include "layoutHierarchyBuilder.h"
#include "utils.h"
#include "crc32.h"

static const std::string SHARED_POINTER_TYPE( "red::SharedPtr" );

std::string GetCPPType( const CLayoutGroupElement* elem )
{
	std::string result;

	if( elem->IsArray() )
	{
		result = "red::DynArray< ";
	}

	if ( elem->GetType() == eLayoutVar_Group )
	{
		const auto* group = elem->GetSubGroup();
		if ( group->IsMessage() )
		{
			std::string ret = SHARED_POINTER_TYPE + "<";
			ret += elem->GetSubGroup()->GetName(); // shared ptr wrappers
			result += ret + ">";
		}
		else
		{
			result += elem->GetSubGroup()->GetName(); // direct structures
		}
	}
	else
	{
		switch ( elem->GetType() )
		{
			case eLayoutVar_Bool: result += "Bool"; break;
			case eLayoutVar_Int8: result += "Int8"; break;
			case eLayoutVar_Int16: result += "Int16"; break;
			case eLayoutVar_Int32: result += "Int32"; break;
			case eLayoutVar_Int64: result += "Int64"; break;
			case eLayoutVar_Uint8: result += "Uint8"; break;
			case eLayoutVar_Uint16: result += "Uint16"; break;
			case eLayoutVar_Uint32: result += "Uint32"; break;
			case eLayoutVar_Uint64: result += "Uint64"; break;
			case eLayoutVar_Float: result += "Float"; break;
			case eLayoutVar_Double: result += "Double"; break;
			case eLayoutVar_String: result += "red::String"; break;
			default: return "unknown";		// Exceptional return
		}
	}

	if( elem->IsArray() )
	{
		result += " >";
	}

	return result;
}

bool GetDefaultValue( const CLayoutGroupElement* elem, std::string& ret )
{
	if ( elem->GetType() == eLayoutVar_String && elem->HasDefaultValue() )
	{
		ret = "\"" + elem->GetDefaultValue() + "\"";
		return true;
	}
	else if ( elem->GetType() == eLayoutVar_Group )
	{
		// ProtoPtr is null initialized by default and structures dont have to be initialized
		return false;
	}
	else if ( elem->HasDefaultValue() )
	{
		ret = elem->GetDefaultValue();
		return true;
	}
	else
	{
		return false;
	}
}

bool GenerateParserCode_CPP_Public( const CLayoutDefinition& def, CTextPrinter& printer, const std::string& namespaceTxt )
{
	// header
	printer.Write( "/// CommLib Protocol Generator (C) 2015 by CD Projekt RED\n" );
	printer.Write( "/// Initial work and implementation by Tomasz \"Dex\" Jonarski\n" );
	printer.Write( "/// All Right Reserved\n" );
	printer.Write( "\n" );
	printer.Write( "#pragma once\n" );
	printer.Write( "\n" );
	printer.Write( "/// AUTOGENERATED FILE - ALL EDITS WILL BE LOST\n" );
	printer.Write( "/// See dev/internal/CommLibProtocol for source\n" );
	printer.Write( "\n" );
	printer.Write( "#include \"../../../../src/common/redMemory/include/sharedPtr.h\"\n\n" );


	// namespace start
	printer.Writef( "namespace %s\n", namespaceTxt.c_str() );
	printer.Write( "{" );
	printer.Indent();	
	printer.Write( "\n" );

	// sort groups in dependency order, if not solvable we cannot generate parser
	CLayoutGroupDependencyAnalyzer depAnalyzer;
	std::vector< const CLayoutGroup* > groupsToPrint;
	if ( !depAnalyzer.CreateOptimalGroupOrder( def, groupsToPrint ) )
		return false;

	// reindex groups to form a proper parent/child hierarchy
	// the ordering indices are saved in the groups
	CLayoutHierarchyBuilder hBuilder;
	if ( !hBuilder.ReindexClasses( def ) )
		return false;

	// forward declare all classes
	for ( const CLayoutGroup* group : groupsToPrint )
	{
		printer.Writef( "class %s;\n", group->GetName().c_str() );
	}
	printer.Write( "\n" );

	// create the message enum
	{
		printer.Write( "// Unique, static ID of the message (name hash)\n" );
		printer.Write( "enum class EMessageID : unsigned int\n" );
		printer.Write( "{\n" );

		std::map< unsigned int, const CLayoutGroup* > groups;

		for ( const CLayoutGroup* group : groupsToPrint )
		{
			if ( group->IsMessage() )
			{
				// compute name CRC
				const unsigned int crc = Crc32( group->GetName().c_str() );
				auto it = groups.find( crc );

				// CRC conflict :( (very rare)
				if ( it != groups.end() )
				{
					fprintf( stderr, "CRC conflict between name of group %s and %s\n", group->GetName().c_str(), it->second->GetName().c_str() );
					return false;
				}

				// keep
				groups[ crc ] = group;

				// write
				printer.Writef( "    %s = 0x%08Xul,\n", group->GetName().c_str(), crc );
			}
		}
		printer.Write( "};\n" );
		printer.Write( "\n" );
	}

	// print groups
	int classIndex = 0;
	for ( const CLayoutGroup* group : groupsToPrint )
	{
		classIndex++;

		printer.Write( "//---------------------------------------------------------------\n" );
		printer.Write( "\n" );

		if ( group->IsMessage() )
			printer.Writef( "// Message %s\n", group->GetName().c_str() );
		else
			printer.Writef( "// Struct %s\n", group->GetName().c_str() );

		if ( group->GetBaseGroup() != nullptr )
		{
			printer.Writef( "class COMMPROTOCOL_API %s : public %s\n", 
				group->GetName().c_str(), group->GetBaseGroup()->GetName().c_str() );
		}
		else if ( group->IsMessage() )
		{
			// messages have implicit base on comm::Message class
			printer.Writef( "class COMMPROTOCOL_API %s : public Message\n",
				group->GetName().c_str() );
		}
		else
		{
			// just a simple structure
			printer.Writef( "class COMMPROTOCOL_API %s\n",
				group->GetName().c_str() );
		}

		// start class
		printer.Write( "{\n" );
		printer.Write( "public:\n" );

		// static name and ID, note that structures don't have static IDs
		printer.Writef( "    static const ProtoStaticString GetStaticName() { return ProtoStaticString( \"%s\" ); };\n", group->GetName().c_str() );

		// class name
		if ( group->IsMessage() )
		{
			// message ID and class index are avaiable only for actuall messages
			printer.Writef( "    static const EMessageID GetStaticID() { return EMessageID::%s; }\n", group->GetName().c_str() );
			printer.Writef( "    static const int GetStaticClassIndex() { return %d; };\n", group->GetClassIndex() );

			// virtualized RTTI
			printer.Writef( "    virtual const ProtoStaticString GetName() const override { return GetStaticName(); }\n" );
			printer.Writef( "    virtual const EMessageID GetID() const override { return GetStaticID(); }\n" );
		}
		printer.Write( "\n" );

		// pseudo RTTI
		if ( group->IsMessage() )
		{
			printer.Write( "public:\n" );

			// implement casting interface from base class
			printer.Writef( "    virtual bool IsA( const EMessageID message ) const override;\n" );
			printer.Write( "\n" );
	}

		// property access		
		printer.Write( "public:\n" );
		for ( size_t i=0; i<group->GetNumElements(); ++i )
		{
			const auto* elem = group->GetElement(i);
			const auto typeName = GetCPPType( elem );
			printer.Writef( "    %s %s%s;%s\n", 
				typeName.c_str(), elem->GetName().c_str(),
				elem->IsArray() || (elem->GetType() == eLayoutVar_String) ? "{ red::PoolBackend() }" : "",
				elem->IsOptional() ? " /*optional*/" : "" );
		}
		printer.Write( "\n" );

		printer.Write( "public:\n" );

		// destructor - empty in non-main shit (inplace data allocation)
		if ( group->IsMessage() )
		{
			printer.Writef( "    virtual ~%s() {};\n", group->GetName().c_str() );	
		}
		else
		{
			printer.Writef( "    inline ~%s() {};\n", group->GetName().c_str() );
		}

		// defaults (via constructor)
		printer.Writef( "    %s()\n", group->GetName().c_str() );
		std::string valuePrefix = ":";
		for ( size_t i=0; i<group->GetNumElements(); ++i )
		{
			const auto* elem = group->GetElement(i);
			std::string defaultValue;
			if ( GetDefaultValue( elem, defaultValue ) )
			{
				printer.Writef( "      %s %s( %s )\n", valuePrefix.c_str(), elem->GetName().c_str(), defaultValue.c_str() );			
				valuePrefix = ",";
			}
		}
		printer.Write( "    {}\n" );
		printer.Write( "\n" );

		// serialization
		{
			printer.Write( "public:\n" );
			printer.Write( "     // Save to data buffer\n" );
			printer.Writef( "    %svoid Serialize( class IProtoWriter& writer ) const;\n",
				group->IsMessage() ? "virtual " : "" );
		}

		// end of class
		printer.Write( "};\n" );
		printer.Write( "\n" );
	}

	// message parsing
	printer.Write( "\n" );
	printer.Writef( "extern %s<Message> COMMPROTOCOL_API Parse( class IProtoReader& reader, Bool allowPartialErrors = true );\n", SHARED_POINTER_TYPE.c_str() );

	// namespace end
	printer.Unindent();	
	printer.Write( "\n" );
	printer.Writef( "} // namespace %s\n", namespaceTxt.c_str() );

	// done
	return true;
}

static std::string GetTypeName( const CLayoutGroupElement* elem )
{
	std::string result;

	if( elem->IsArray() )
	{
		result = "red::DynArray< ";
	}

	switch ( elem->GetType() )
	{
		case eLayoutVar_Invalid: result += "invalid";
		case eLayoutVar_Bool: result += "Bool";
		case eLayoutVar_Int8: result += "Int8";
		case eLayoutVar_Int16: result += "Int16";
		case eLayoutVar_Int32: result += "Int32";
		case eLayoutVar_Int64: result += "Int64";
		case eLayoutVar_Uint8: result += "Uint8";
		case eLayoutVar_Uint16: result += "Uint16";
		case eLayoutVar_Uint32: result += "Uint32";
		case eLayoutVar_Uint64: result += "Uint64";
		case eLayoutVar_Float: result += "Float";
		case eLayoutVar_Double: result += "Double";
		case eLayoutVar_String: result += "red::String";
		case eLayoutVar_Enum: result += "$enum"; // TODO
		case eLayoutVar_Group:
		{
			if ( elem->GetSubGroup()->IsMessage() )
				result += SHARED_POINTER_TYPE + "< " + elem->GetSubGroup()->GetName() + " >";
			else
				result += elem->GetSubGroup()->GetName();
		}
		default: return "unknown";		// Exceptional return
	}

	if( elem->IsArray() )
	{
		result += " >";
	}

	return result;
}

bool GenerateParserCode_CPP_Private( const CLayoutDefinition& def, CTextPrinter& printer, const std::string& namespaceTxt, const std::string& includePath )
{
	// no data
	if ( def.GetGroups().empty() )
		return false;

	// header
	printer.Write( "/// CommLib Protocol Generator (C) 2015 by CD Projekt RED\n" );
	printer.Write( "/// Initial work and implementation by Tomasz \"Dex\" Jonarski\n" );
	printer.Write( "/// All Right Reserved\n" );
	printer.Write( "\n" );
	printer.Write( "/// AUTOGENERATED FILE - ALL EDITS WILL BE LOST\n" );
	printer.Write( "\n" );

	// include definitions
	printer.Writef( "#include \"%s\"\n", includePath.c_str() );
	printer.Write( "\n" );

	// namespace start
	printer.Writef( "namespace %s\n", namespaceTxt.c_str() );
	printer.Write( "{" );
	printer.Indent();	
	printer.Write( "\n" );

	// parsing functions forward declaration
	{
		printer.Write( "// All parsing functions (forward declaration) called when the type is strictly known\n" );
		for ( const CLayoutGroup* group : def.GetGroups() )
		{
			printer.Writef( "bool Parse_%s( %s& ret, IProtoReader& reader );\n"
				, group->GetName().c_str(), group->GetName().c_str() );
		}
		printer.Write( "\n" );
	}

	// dynamic parsing functions
	{
		// collect classes requiring dynamic parsing (derivable classes that are used in arrays or non-inplace groups)
		std::vector< const CLayoutGroup* > dynamicGroups;
		for ( const CLayoutGroup* group : def.GetGroups() )
		{
			for ( size_t i=0; i<group->GetNumElements(); ++i )
			{
				const auto* elem = group->GetElement(i);
				if ( elem->IsArray() || elem->GetType() == eLayoutVar_Group )
				{
					const CLayoutGroup* dynamicGroup = elem->GetSubGroup();
					if ( dynamicGroup && dynamicGroup->IsMessage() )
					{
						const auto it = std::find( dynamicGroups.begin(), dynamicGroups.end(), dynamicGroup );
						if ( it == dynamicGroups.end() )
							dynamicGroups.push_back( dynamicGroup );
					}
				}
			}
		}

		// write functions only if needed
		if ( !dynamicGroups.empty() )
		{
			for ( const CLayoutGroup* group : dynamicGroups )
			{
				printer.Writef( "// Dynamic parsing function for '%s'\n", group->GetName().c_str() );

				// function header
				printer.Writef( "bool ParseDynamic_%s( %s<%s>& retPtr, IProtoReader& reader )\n" , group->GetName().c_str(), SHARED_POINTER_TYPE.c_str(), group->GetName().c_str() );
				printer.Write( "{" );
				printer.Indent();
				printer.Write( "\n" );

				// enumerate possible groups
				std::vector< const CLayoutGroup* > childGroups;
				group->EnumChildGroups( childGroups );

				// read type name
				printer.Write( "// Read block ID from the stream\n" );
				printer.Write( "const auto id = reader.PeekTypeHash();\n" );

				// check options
				const char* ifCondition = "if";
				for ( const CLayoutGroup* childGroup : childGroups )
				{
					const char* type = childGroup->GetName().c_str();

					printer.Writef( "%s ( id == EMessageID::%s )\n", ifCondition, type );
					printer.Write(  "{\n");
					printer.Writef( "    %s* instance = RED_NEW( %s );\n", type, type );
					printer.Write(  "    retPtr.Reset( instance );\n" );
					printer.Writef( "    return Parse_%s( *instance, reader );\n", type );
					printer.Write(  "}\n");

					// other stuff
					ifCondition = "else if";
				}

				// NULL
				printer.Write( "else if ( id == (EMessageID)0 ) // NULL\n");
				printer.Write( "{\n" );
				printer.Write( "    retPtr.Reset();\n" );
				printer.Write( "    return true;\n" );
				printer.Write( "}\n" );

				// unknown type
				printer.Write( "else\n");
				printer.Write( "{\n");
				printer.Writef( "    reader.Error( \"Unmatched message ID 0x%%08X\", id );\n" );
				printer.Write(  "    return false;\n" );
				printer.Write( "}\n");

				// function tail
				printer.Unindent();	
				printer.Write( "\n" );
				printer.Write( "}\n" );
				printer.Write( "\n" );
			}
		}
	}

	// print groups
	for ( const CLayoutGroup* group : def.GetGroups() )
	{
		printer.Write( "//---------------------------------------------------------------\n" );
		printer.Write( "\n" );

		// function header
		printer.Writef( "bool Parse_%s( %s& ret, IProtoReader& reader )\n", group->GetName().c_str(), group->GetName().c_str() );
		printer.Write( "{" );
		printer.Indent();
		printer.Write( "\n" );

		// validate type hash
		{
			const auto typeName = group->GetName();
			const unsigned int typeNameHash = Crc32( typeName.c_str() );
			printer.Writef( "if ( !reader.BeginObject( \"%s\", 0x%08Xull ) )\n", typeName.c_str(), typeNameHash );
			printer.Write(  "{\n" );
			printer.Write(  "    reader.EndObject();\n" );
			printer.Write(  "    return false;\n" );
			printer.Write(  "}\n\n" );
		}

		// the parsing loop
		printer.Write( "bool noErrors = true;\n");
		printer.Write( "const int numProps = reader.StartPropsBlock();\n" );			
		printer.Write( "for ( int i=0; i<numProps; ++i )\n");
		printer.Write( "{\n" );

		// read property name
		printer.Write( "    unsigned int propHash = 0;\n" );
		printer.Write( "    if ( !reader.BeginProperty( propHash ) )\n" );
		printer.Write( "    {\n");
		printer.Write( "         noErrors = false;\n");
		printer.Write( "         break;\n" );
		printer.Write( "    }\n");
		printer.Write( "\n" );

		// parsing switch
		printer.Write( "    switch ( propHash )\n" );
		printer.Write( "    {\n" );

		// get all savable elements
		std::vector< const CLayoutGroupElement* > allElems;
		group->GetWritableElements( allElems );

		// add parsing code for each element
		for ( size_t i=0; i<allElems.size(); ++i )
		{
			const auto* elem = allElems[i];

			const unsigned int nameHash = Crc32( elem->GetName().c_str() );
			printer.Writef( "        case 0x%08Xull: // %s\n", nameHash, elem->GetName().c_str() );
			printer.Writef( "            {\n" );

			std::string elemAccessName = elem->GetName().c_str();

			if( elem->IsArray() )
			{
				printer.Writef( "                const Uint32 count = (Uint32) reader.BeginArray();\n" );
				printer.Writef( "                ret.%s.Resize( count );\n ", elem->GetName().c_str() );
				printer.Writef( "                for ( Uint32 j=0; j<count; ++j )\n");
				printer.Writef( "                {\n ");

				elemAccessName += "[j]";
			}

			// parsing depends on type
			switch ( elem->GetType() )
			{
				case eLayoutVar_Uint8:
					printer.Writef( "            noErrors &= reader.ReadUint8( ret.%s );\n", elemAccessName.c_str() );
					break;

				case eLayoutVar_Uint16:
					printer.Writef( "            noErrors &= reader.ReadUint16( ret.%s );\n", elemAccessName.c_str() );
					break;

				case eLayoutVar_Uint32:
					printer.Writef( "            noErrors &= reader.ReadUint32( ret.%s );\n", elemAccessName.c_str() );
					break;

				case eLayoutVar_Uint64:
					printer.Writef( "            noErrors &= reader.ReadUint64( ret.%s );\n", elemAccessName.c_str() );
					break;

				case eLayoutVar_Int8:
					printer.Writef( "            noErrors &= reader.ReadInt8( ret.%s );\n", elemAccessName.c_str() );
					break;

				case eLayoutVar_Int16:
					printer.Writef( "            noErrors &= reader.ReadInt16( ret.%s );\n", elemAccessName.c_str() );
					break;

				case eLayoutVar_Int32:
					printer.Writef( "            noErrors &= reader.ReadInt32( ret.%s );\n", elemAccessName.c_str() );
					break;

				case eLayoutVar_Int64:
					printer.Writef( "            noErrors &= reader.ReadInt64( ret.%s );\n", elemAccessName.c_str() );
					break;

				case eLayoutVar_Bool:
					printer.Writef( "            noErrors &= reader.ReadBool( ret.%s );\n", elemAccessName.c_str() );
					break;

				case eLayoutVar_Double:
					printer.Writef( "            noErrors &= reader.ReadDouble( ret.%s );\n", elemAccessName.c_str() );
					break;

				case eLayoutVar_Float:
					printer.Writef( "            noErrors &= reader.ReadFloat( ret.%s );\n", elemAccessName.c_str() );
					break;

				case eLayoutVar_String:
					printer.Writef( "            noErrors &= reader.ReadString( ret.%s );\n", elemAccessName.c_str() );
					break;

				case eLayoutVar_Group:
				{
					const auto subGroup = elem->GetSubGroup();
					if ( subGroup->IsMessage() )
						printer.Writef( "            noErrors &= ParseDynamic_%s( ret.%s, reader );\n", 
							subGroup->GetName().c_str(), elemAccessName.c_str() );
					else
						printer.Writef( "            noErrors &= Parse_%s( ret.%s, reader );\n", 
							subGroup->GetName().c_str(), elemAccessName.c_str() );

					break;
				}

				default:
					break;
			}

			if( elem->IsArray() )
			{
				printer.Writef( "                }\n");
				printer.Writef( "                reader.EndArray();\n" );
			}

			printer.Writef( "            }\n" );
			printer.Write(  "            break;\n" );
		}

		printer.Write( "    }\n" );			

		// unsupported property
		printer.Write( "    reader.EndProperty(); // skip any unparsed data\n" );
		printer.Write( "}\n" );			

		// function tail
		printer.Write( "reader.EndObject();\n" );
		printer.Write( "return noErrors; // everything was read" );
		printer.Unindent();	
		printer.Write( "\n" );
		printer.Write( "}\n" );
		printer.Write( "\n" );
	}

	// method implementations
	for ( const CLayoutGroup* group : def.GetGroups() )
	{
		printer.Write( "//---------------------------------------------------------------\n" );
		printer.Write( "\n" );

		// IsA - only for messages
		if ( group->IsMessage() )
		{
			printer.Writef( "bool %s::IsA( const EMessageID message ) const\n", group->GetName().c_str() );
			printer.Write( "{" );
			printer.Indent();
			printer.Write( "\n" );

			const CLayoutGroup* thisGroup = group;
			while ( thisGroup != nullptr )
			{
				// id check
				printer.Writef( "if ( message == EMessageID::%s ) return true;\n", thisGroup->GetName().c_str() );
				
				// go to parent
				thisGroup = thisGroup->GetBaseGroup();
			}

			// not this class
			printer.Write( "return false;" );

			printer.Unindent();	
			printer.Write( "\n" );
			printer.Write( "}\n" );
			printer.Write( "\n" );
		}

		// Save method
		{
			printer.Writef( "void %s::Serialize( class IProtoWriter& writer ) const\n", group->GetName().c_str() );
			printer.Write( "{" );
			printer.Indent();
			printer.Write( "\n" );

			// write type hash (always the first thing even for structures)
			// NOTE that this function is not recursive
			const unsigned int typeHash = Crc32( group->GetName().c_str() );
			printer.Writef( "writer.BeginObject( \"%s\", 0x%08Xull );\n", group->GetName().c_str(), typeHash );

			// get all of the stuff to write
			std::vector< const CLayoutGroupElement* > elems;
			group->GetWritableElements( elems );

			// being parameter group, notify system about the number of expected parameters
			printer.Writef( "writer.BeginParams( %d );\n", elems.size() );
			printer.Writef( "int numSavedParams = 0;\n" );

			// start writing stuff into the protocol buffer
			for ( const CLayoutGroupElement* elem : elems )
			{
				printer.Writef( "// Field '%s'\n", elem->GetName().c_str() );

				// some parameters with default values can be checked before written (saving space)
				bool ifAdded = false;
				{
					std::string defValue;
					if ( GetDefaultValue( elem, defValue ) )
					{
						printer.Writef( "if ( %s != %s )\n", elem->GetName().c_str(), defValue.c_str() );
						printer.Write( "{" );
						printer.Indent();
						printer.Write( "\n" );

						ifAdded = true;
					}					
				}

				// write parameter name
				const unsigned int nameHash = Crc32( elem->GetName().c_str() );
				printer.Writef( "writer.BeginParam( \"%s\", 0x%08Xull );\n", elem->GetName().c_str(), nameHash );

				std::string elemAccessName = elem->GetName();

				if( elem->IsArray() )
				{
					const std::string& typeName = GetCPPType( elem );
					const unsigned int typeNameHash = Crc32( typeName.c_str() );
					printer.Writef( "writer.BeginArray( \"%s\", 0x%08Xull, %s.Size() );\n", typeName.c_str(), typeNameHash, elem->GetName().c_str() );

					printer.Writef( "for ( Uint32 i=0; i<(Uint32)%s.Size(); ++i )\n", elem->GetName().c_str() );
					printer.Write ( "{\n" );

					elemAccessName += "[i]";
				}

				// write parameter value
				switch ( elem->GetType() )
				{
					case eLayoutVar_Group:
					{
						const auto typeName = elem->GetSubGroup()->GetName();
						const unsigned int typeNameHash = Crc32( typeName.c_str() );

						// different access method based on the type
						if ( elem->GetSubGroup()->IsMessage() )
						{
							printer.Writef( "if ( %s )\n", elemAccessName.c_str() );
							printer.Write ( "{\n" );
							printer.Writef( "    %s->Serialize( writer );\n", elemAccessName.c_str() );
							printer.Write ( "}\n" );
							printer.Write ( "else\n" );
							printer.Write ( "{\n" );
							printer.Writef( "    writer.WriteNull();\n" );
							printer.Write ( "}\n" );
						}
						else
						{
							printer.Writef( "%s.Serialize( writer );\n", elemAccessName.c_str() );
						}

						break;
					}

					case eLayoutVar_String:
						printer.Writef( "writer.WriteString( %s );\n", elemAccessName.c_str() );
						break;

					case eLayoutVar_Bool:
						printer.Writef( "writer.WriteBool( %s );\n", elemAccessName.c_str() );
						break;

					case eLayoutVar_Float:
						printer.Writef( "writer.WriteFloat( %s );\n", elemAccessName.c_str() );
						break;

					case eLayoutVar_Double:
						printer.Writef( "writer.WriteDouble( %s );\n", elemAccessName.c_str() );
						break;

					case eLayoutVar_Uint8:
						printer.Writef( "writer.WriteUint8( %s );\n", elemAccessName.c_str() );
						break;

					case eLayoutVar_Uint16:
						printer.Writef( "writer.WriteUint16( %s );\n", elemAccessName.c_str() );
						break;

					case eLayoutVar_Uint32:
						printer.Writef( "writer.WriteUint32( %s );\n", elemAccessName.c_str() );
						break;

					case eLayoutVar_Uint64:
						printer.Writef( "writer.WriteUint64( %s );\n", elemAccessName.c_str() );
						break;

					case eLayoutVar_Int8:
						printer.Writef( "writer.WriteInt8( %s );\n", elemAccessName.c_str() );
						break;

					case eLayoutVar_Int16:
						printer.Writef( "writer.WriteInt16( %s );\n", elemAccessName.c_str() );
						break;

					case eLayoutVar_Int32:
						printer.Writef( "writer.WriteInt32( %s );\n", elemAccessName.c_str() );
						break;

					case eLayoutVar_Int64:
						printer.Writef( "writer.WriteInt64( %s );\n", elemAccessName.c_str() );
						break;

					default:
						break;
				}

				if( elem->IsArray() )
				{
					printer.Write ( "}\n" );
					printer.Writef( "writer.EndArray();\n" );
				}

				// end param (skip block)
				printer.Writef( "numSavedParams += 1;\n" );
				printer.Writef( "writer.EndParam();" );

				// separator
				if ( ifAdded )
				{
					printer.Unindent();	
					printer.Write( "\n" );
					printer.Write( "}\n" );
					printer.Write( "\n" );
				}
				else
				{
					printer.Write( "\n" );
					printer.Write( "\n" );
				}
			}

			// end writing group
			printer.Writef( "writer.EndParams( numSavedParams );\n" );
			printer.Writef( "writer.EndObject();\n" );

			printer.Unindent();	
			printer.Write( "\n" );
			printer.Write( "}\n" );
			printer.Write( "\n" );
		}
	}

	// final parsing function
	{
		printer.Writef( "%s< Message > Parse( class IProtoReader& reader, Bool allowParialErrors )\n", SHARED_POINTER_TYPE.c_str() );
		printer.Write( "{" );
		printer.Indent();
		printer.Write( "\n" );

		// read type name
		printer.Write( "// Read block ID from the stream\n" );
		printer.Write( "const auto id = reader.PeekTypeHash();\n" );

		// check options
		printer.Writef( "%s< Message > ret;\n", SHARED_POINTER_TYPE.c_str() );
		printer.Write( "switch ( id )\n");
		printer.Write( "{\n");
		for ( const CLayoutGroup* childGroup : def.GetGroups() )
		{
			const char* type = childGroup->GetName().c_str();

			if ( childGroup->IsMessage() )
			{
				printer.Writef( "    case EMessageID::%s:\n", type );
				printer.Write ( "    {\n" );
				printer.Writef( "         %s* instance = RED_NEW( %s );\n", type, type );
				printer.Write(  "         ret.Reset( instance );\n" );
				printer.Writef( "         if ( !Parse_%s( *instance, reader ) && !allowParialErrors ) ret.Reset();\n", type );
				printer.Write ( "         break;\n" );
				printer.Write ( "    }\n" );
			}
		}

		printer.Writef( "    default:\n" );
		printer.Writef( "        reader.Error( \"Unmatched message ID 0x%%08X\", id );\n" );
		printer.Write(  "}\n");

		// not parsed
		printer.Write(  "return ret;\n" );

		printer.Unindent();	
		printer.Write( "\n" );
		printer.Write( "}\n" );
		printer.Write( "\n" );
	}

	// namespace end
	printer.Unindent();	
	printer.Write( "\n" );
	printer.Writef( "} // namespace %s\n", namespaceTxt.c_str() );

	// done
	return true;
}
